package com.rock.securityresearch.util.http

import cats.{Functor, Parallel}
import cats.effect.{IO, Sync}
import okhttp3.{MediaType, Request, RequestBody, Response}
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import cats.effect.unsafe.implicits.global

import scala.Tuple.FlatMap

@main
def main: Unit = {
//    val res: IO[HttpResponse] = Http.make().get("https://baidu.com")
//    val r = res.unsafeRunSync().bodyAsString()
//
//    Http.make().parGet(List("https://baidu.com", "https://baidu.com", "https://baidu.com")).unsafeRunSync()
//    println(s"r is $r")
}


trait Http[F[_]] {
    def get(url: String, headers: Map[String, String] = Map.empty): F[HttpResponse]

    def post(url: String, data: String | Array[Byte] = Array.empty, headers: Map[String, String] = Map.empty): F[HttpResponse]

    def parGet(urls: List[String], headers: Map[String, String] = Map.empty)(using P: Parallel[F]): F[List[HttpResponse]]

    def parPost(urls: List[String], data: String | Array[Byte] = Array.empty, headers: Map[String, String] = Map.empty)(using P: Parallel[F]): F[List[HttpResponse]]
}

object Http {

    import okhttp3.OkHttpClient
    import cats.syntax.parallel.*
    import org.typelevel.log4cats.Logger
    import cats.implicits.catsSyntaxApply
    import java.time.Duration
    import cats.implicits.catsSyntaxFlatMapOps
    import cats.implicits.toFlatMapOps
    import cats.syntax.functor.toFunctorOps

    private lazy val client = OkHttpClient.Builder().readTimeout(Duration.ofSeconds(60)).build()

    def make()(using Logger: Logger[IO]): Http[IO] = {
        Http[IO]
    }

    def apply[F[_] : Sync : Logger]: Http[F] = new Http[F] {
        def get(url: String, headers: Map[String, String] = Map.empty): F[HttpResponse] = {
            for {
                _ <- Logger[F].info(s"Begin Get $url")
                start <- Sync[F].delay(System.currentTimeMillis())
                res <- Sync[F].blocking {
                            val reqBuilder = Request.Builder().url(url)
                            headers.foreach((k, v) => reqBuilder.header(k, v))
                            val res = toResponse(client.newCall(reqBuilder.build).execute)
                            res
                        }
                end <- Sync[F].delay(System.currentTimeMillis())
                _ <- Logger[F].info(s"End Get $url, cost ${end - start} ms")
            } yield res
        }

        def post(url: String, data: String | Array[Byte] = Array.empty, headers: Map[String, String] = Map.empty): F[HttpResponse] = {
            for {
                _ <- Logger[F].info(s"Begin Post $url")
                start <- Sync[F].delay(System.currentTimeMillis())
                res <- Sync[F].blocking {
                        val dataBytes = data match {
                            case d: Array[Byte] => d
                            case d: String => d.getBytes("utf-8")
                        }
                        val requestBody = RequestBody.create(
                            dataBytes,
                            MediaType.parse("application/json")
                        );
                        val reqBuilder = Request.Builder().url(url).post(requestBody)
                        headers.foreach((k, v) => reqBuilder.header(k, v))
                        toResponse(client.newCall(reqBuilder.build).execute)
                    }
                end <- Sync[F].delay(System.currentTimeMillis())
                _ <- Logger[F].info(s"End Post $url, cost ${end - start} ms")
            } yield res
        }

        def parGet(urls: List[String], headers: Map[String, String] = Map.empty)(using P: Parallel[F]): F[List[HttpResponse]] = {
            for {
                _ <- Logger[F].info(s"Begin parGet")
                start <- Sync[F].delay(System.currentTimeMillis())
                res <- urls.parTraverse(url => get(url, headers))
                end <- Sync[F].delay(System.currentTimeMillis())
                _ <- Logger[F].info(s"End parGet, cost ${end - start} ms")
            } yield res
        }

        def parPost(urls: List[String], data: String | Array[Byte] = Array.empty, headers: Map[String, String] = Map.empty)(using P: Parallel[F]): F[List[HttpResponse]] = {
            for {
                _ <- Logger[F].info(s"Begin parPost")
                start <- Sync[F].delay(System.currentTimeMillis())
                res <- urls.parTraverse(url => post(url, data, headers))
                end <- Sync[F].delay(System.currentTimeMillis())
                _ <- Logger[F].info(s"End parPost, cost ${end - start} ms")
            } yield res
        }

        private def toResponse(resp: Response): HttpResponse = {
            val status = resp.code()
            val body = resp.body().bytes()
            val headersNames = resp.headers().names()
            var headers = Map.empty[String, String]
            headersNames.forEach { name =>
                val value = String.join(",", resp.headers().values(name))
                headers += name -> value
            }
            HttpResponse(status, body, headers)
        }
    }
}
